﻿using System;

namespace ComputedTable.Version10
{
    public class Cell<TRowId, TColumnId>
    {
        private int? valueAsInt;
        private decimal? valueAsDecimal;
        private DateTime? valueAsDateTime;
        private string valueAsText;
        private object value;

        public Cell(TRowId row, TColumnId column)
        {
            this.RowId = row;
            this.ColumnId = column;
        }

        public TRowId RowId { get; private set; }
        public TColumnId ColumnId { get; private set; }
        public Func<CellOperationResult> Formula { get; set; }

        public int GetValueAsInt()
        {
            if (!this.valueAsDecimal.HasValue)
            {
                this.valueAsInt = this.ConvertValue<int>();
            }

            return this.valueAsInt.Value;
        }

        public decimal GetValueAsDecimal()
        {
            if (!this.valueAsDecimal.HasValue)
            {
                this.valueAsDecimal = this.ConvertValue<decimal>();
            }

            return this.valueAsDecimal.Value;
        }

        public DateTime GetValueAsDateTime()
        {
            if (!this.valueAsDateTime.HasValue)
            {
                this.valueAsDateTime = this.ConvertValue<DateTime>();
            }

            return this.valueAsDateTime.Value;
        }

        public string GetValueAsText()
        {
            if (this.valueAsText == null)
            {
                this.valueAsText = this.ConvertValue<string>();
            }

            return this.valueAsText;
        }

        public object GetValue()
        {
            if (this.value == null)
            {
                throw new ComputedTableException("Value not assigned", this.RowId.ToString(), this.ColumnId.ToString());
            }

            return this.value;
        }

        public object Value
        {
            get { return this.value; }
            set
            {
                this.ResetInternalValues();
                this.value = value;
            }
        }

        public CellOperationResult Compute()
        {
            if (this.Formula == null)
            {
                return CellOperationResult.SucceedCellOperationResult;
            }

            var calculationResult = this.Formula();
            if (calculationResult.Succeed)
            {
                this.Value = calculationResult.Value;
            }

            return calculationResult;
        }


        private void ResetInternalValues()
        {
            this.valueAsInt = null;
            this.valueAsDecimal = null;
            this.valueAsDateTime = null;
            this.valueAsText = null;
        }

        private T ConvertValue<T>()
        {
            var v = this.GetValue(); // an exception is raised here if value is not assigned.

            try
            {
                return (T)v;
            }
            catch (Exception ex)
            {
                string message = string.Format("Failed to convert value from type {0} to type {1}", v.GetType().Name, typeof(T).Name);
                throw new InvalidCastException(message, ex);
            }
        }
    }
}
